/*
	ahci_driver.c

	contains AHCI boot driver code

	Author: Aidan Goddard 27/1/14
*/

#include"ahci.h"

// global control variables
HANDLE boot_device;
uint32_t boot_drive;
uint64_t boot_key;
volatile uint32_t *boot_abar;
ISR_VECTOR g_vector;
uint64_t g_isr_key;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// driver ISR routine
static void AHCI_ISR(uint32_t vector)
{
	//printf("\n\n AHCI ISR FIRED!!!");
	// finished
	return;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// top level read function - must be called with interrupts enabled
static SCH_CLS read_lock;
uint32_t AHCI_BOOT_READ(char *buffer, uint64_t sec_start, uint16_t sec_count)
{
	if(!boot_device)
	{
		return AHCI_BOOT_READ_DISABLED;
	}

	// get lock on data read
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli":::"memory");
	__asm__ __volatile__("":::"memory");
	uint32_t CPU = GetCPUID();
	BakeryEnter(&read_lock, CPU);

	// construct command table and command header 0
	uint32_t *cmd = (uint32_t*)(KOFFSET + 0x4a00000);
	cmd[0] = (1 << 16) | 5;// | (1 << 10);
	cmd[1] = 0;
	cmd[2] = 0x4a02000;
	cmd[3] = 0;
	cmd[4] = 0;
	cmd[5] = 0;
	cmd[6] = 0;
	cmd[7] = 0;

	uint32_t *tbl = (uint32_t*)(KOFFSET + 0x4a02000);
	tbl[0] = 0x27 | (1 << 15) | (0x25 << 16);	// type | command=true | command register
	tbl[1] = (sec_start & 0xffffff) | (1 << 30);						// LBA 23:0 | device register
	tbl[2] = (sec_start >> 24) & 0xffffffff;									// LBA 47:24 | feature register
	tbl[3] = sec_count;//0x2000;									// count 15:0 | ICC | control register
	tbl[4] = 0;									// reserved

	uint64_t address = (uint64_t)buffer;
	tbl[32] = (uint32_t)(address & 0xffffffff);			// data base address
	tbl[33] = (uint32_t)(address >> 32);					// data base address upper 32 bits
	tbl[34] = 0;					// reserved
	tbl[35] = ((sec_count * 512) - 1);// | (1 << 31);//0x3fffff;//	// data byte count + interrupt on completion (bit 31)

	// set the command issue bit for slot 0 (bit 0)
	uint32_t reg_base_num = (0x100 + (boot_drive * 0x80)) / 4;
	__asm__ __volatile__("mfence":::"memory");
	boot_abar[reg_base_num + 14] = 1;
	__asm__ __volatile__("mfence":::"memory");

	// spin until data is received
	while(1)
	{
		__asm__ __volatile__("mfence":::"memory");
		uint32_t in = boot_abar[reg_base_num + 14];
		__asm__ __volatile__("mfence":::"memory");

		if((in & 1) == 0)
		{
			break;
		}
	}

	// unlock
	BakeryLeave(&read_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti":::"memory");
	__asm__ __volatile__("":::"memory");

	// finished, return success
	return AHCI_READ_SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// function to drop the ahci boot read driver
uint32_t AHCI_DROP()
{
	// first get the PCIE handle and disable the driver
	HANDLE handle = boot_device;
	boot_device = 0;

	// unreserve the PCIE device
	uint32_t e = __KLIB__PCIEFreeDevice(handle, boot_key);
	//printf("\n0x%x", e);

	// disable the ISR
	e = __KLIB__ISRUnregister(g_vector, g_isr_key);
	//printf("\n0x%x", e);
	e = __KLIB__ISRFree(g_vector, g_isr_key);
	//printf("\n0x%x", e);

	boot_abar = 0;


	return AHCI_INIT_SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// driver initialisation
uint32_t AHCI_INIT()
{
	// get all storage class device handles
	uint32_t storage_device_count;
	uint32_t error = __KLIB__PCIEGetDeviceCountOfClass(&storage_device_count, 0x010000);
	if(error == PCIE_NO_DEVICES)
	{
		return AHCI_INIT_FAIL_NO_DEVICES;
	}

	// get all storage device handles
	HANDLE tmp_handles[64];	// not expecting more than 64 storage device controllers to be available
	error = __KLIB__PCIEGetDevicesOfClass(tmp_handles, 0x010000);

	// for each storage device, check if it is an AHCI controller
	uint32_t device_count;
	HANDLE device_handles[4];
	uint64_t device_keys[4];
	uint32_t i;
	for(i = 0; i < storage_device_count; i++)
	{
		// get class code for device
		uint32_t tmp_class_code, tmp_vendor, tmp_device;
		error = __KLIB__PCIEGetDeviceIDs(tmp_handles[i], &tmp_class_code, &tmp_vendor, &tmp_device);
		if(error != PCIE_SUCCESS)
		{
			continue;
		}

		// check the class code
		if(tmp_class_code == 0x010601 && device_count < 4)
		{
			device_handles[device_count++] = tmp_handles[i];
		}
	}

	// have the device handles, attempt to reserve them
	for(i = 0; i < device_count; i++)
	{
		// attempt to reserve the device
		device_keys[i] = 0;
		error = __KLIB__PCIEReserveDevice(device_handles[i], &device_keys[i]);
		if(error != PCIE_SUCCESS)
		{
			continue;
		}
	}

	// handle of the boot device
	boot_device = 0;
	boot_drive = 0;

	// should have reserved all AHCI storage devices
	// now need to find the boot device + drive
	// 1) get all drives for device
	// 2) read in sector zero for each drive
	// 3) check the full 512-bytes match the currently-loaded boot code at 0x1000 + KOFFSET
	// 4) save details and register boot drive + driver with OS
	// for each device
	for(i = 0; i < device_count; i++)
	{
		// first check if device is MSI-capable
		// get base of capabilities list
		uint32_t cap_base = 0x34 / 4;
		__KLIB__PCIEConfigRead(device_handles[i], device_keys[i], cap_base, &cap_base);
		uint32_t msi_cap = 0;

		// capabilites search loop
		while(1)
		{
			// read in the first dword of the capabilities structure
			uint32_t buff;
			cap_base = cap_base / 4;
			__KLIB__PCIEConfigRead(device_handles[i], device_keys[i], cap_base, &buff);

			// get capability ID and ptr to next capability
			uint32_t cap_id = buff & 0xff;
			uint32_t next_cap = (buff >> 8) & 0xff;

			// check if MSI structure
			if(cap_id == 0x05)
			{
				msi_cap = 1;
				break;
			}

			// not the MSI cap structure, get ptr to next one
			cap_base = next_cap;
			if(cap_base == 0)
			{
				break;
			}
		}

		// is it MSI capable?
		if(!msi_cap)
		{
			// no, move on to next device
			continue;
		}

		// it is MSI capable
		// get base address of controller
		uint32_t base_address;
		__KLIB__PCIEConfigRead(device_handles[i], device_keys[i], 0x24/4, &base_address);
		volatile uint32_t *abar = (uint32_t*)(uint64_t)base_address;

		// perform BIOS handover of controller
		__asm__ __volatile__("mfence":::"memory");
		uint32_t extended_cap = abar[9];
		__asm__ __volatile__("mfence":::"memory");

		//printf("\n0x%x", extended_cap);

		// check if BIOS handover supported
		if(extended_cap & 1)
		{
			printf("\nAHCI BIOS handoff commencing...");

			// set OS ownership bit
			__asm__ __volatile__("mfence":::"memory");
			uint32_t in = abar[10];
			__asm__ __volatile__("mfence":::"memory");
			abar[10] = in & 2;
			__asm__ __volatile__("mfence":::"memory");

			// spin on BIOS Ownership bit till cleared to 0
			while(in & 1)
			{
				__asm__ __volatile__("mfence":::"memory");
				uint32_t in = abar[10];
				__asm__ __volatile__("mfence":::"memory");
			}

			printf(" done");
		}

		// enable the device in AHCI mode and disable interrupts
		__asm__ __volatile__("mfence":::"memory");
		uint32_t GHC = abar[1];
		__asm__ __volatile__("mfence":::"memory");
		GHC = GHC | (1 << 31);		// AHCI mode
		GHC = GHC & 0xfffffffd;		// global disable interrupts
		abar[1] = GHC;
		__asm__ __volatile__("mfence":::"memory");

		// get implemented ports & details for each port
		__asm__ __volatile__("mfence":::"memory");
		uint32_t ports = abar[3];
		__asm__ __volatile__("mfence":::"memory");

		// for each port
		int j;
		for(j = 0; j < 32; j++)
		{
			// check if boot device was already found
			if(boot_device)
			{
				break;
			}

			// check if port is available
			if((ports >> j) & 1)
			{
				// port is available, get the physical connection state
				uint32_t reg_base_num = (0x100 + (j * 0x80)) / 4;
				__asm__ __volatile__("mfence":::"memory");
				uint32_t state = abar[reg_base_num + 10];
				__asm__ __volatile__("mfence":::"memory");
				uint32_t IPM, SPD, DET;
				IPM = state >> 8;
				SPD = (state >> 4) & 0xf;
				DET = state & 0xf;

				// check if port is detected and powered on
				if(DET == 0x3 && IPM == 0x1)
				{
					// port detected and powered on
					// TODO: add code to power on a detected, but slumbering device
					// read in sector 0 for device
					// stop command running and received FIS
					__asm__ __volatile__("mfence":::"memory");
					uint32_t PxCMD = abar[reg_base_num + 6];
					__asm__ __volatile__("mfence":::"memory");
					abar[reg_base_num + 6] = PxCMD & 0xffffffee;
					__asm__ __volatile__("mfence":::"memory");

					// wait until command running and received FIS running are zero
					__asm__ __volatile__("mfence":::"memory");
					PxCMD = abar[reg_base_num + 6];
					__asm__ __volatile__("mfence":::"memory");
					while(PxCMD & 0xc000)
					{
						__asm__ __volatile__("mfence":::"memory");
						uint32_t PxCMD = abar[reg_base_num + 6];
						__asm__ __volatile__("mfence":::"memory");
					}

					// command processing has stopped
					// set command list and received FIS base addresses
					__asm__ __volatile__("mfence":::"memory");
					abar[reg_base_num + 0] = 0x4a00000;
					abar[reg_base_num + 1] = 0;
					abar[reg_base_num + 2] = 0x4a01000;
					abar[reg_base_num + 3] = 0;
					__asm__ __volatile__("mfence":::"memory");

					// restart command processing and make sure device is active
					__asm__ __volatile__("mfence":::"memory");
					abar[reg_base_num + 6] = PxCMD | 0x11 | (1 << 28);
					__asm__ __volatile__("mfence":::"memory");

					// wait until command running and received FIS running are one
					__asm__ __volatile__("mfence":::"memory");
					PxCMD = abar[reg_base_num + 6];
					__asm__ __volatile__("mfence":::"memory");
					while((PxCMD & 0xc000) != 0xc000)
					{
						__asm__ __volatile__("mfence":::"memory");
						uint32_t PxCMD = abar[reg_base_num + 6];
						__asm__ __volatile__("mfence":::"memory");
					}

					// construct command table and command header 0
					uint32_t *cmd = (uint32_t*)(KOFFSET + 0x4a00000);
					cmd[0] = (1 << 16) | (1 << 10) | 5;
					cmd[1] = 0;
					cmd[2] = 0x4a02000;
					cmd[3] = 0;
					cmd[4] = 0;
					cmd[5] = 0;
					cmd[6] = 0;
					cmd[7] = 0;

					uint32_t *tbl = (uint32_t*)(KOFFSET + 0x4a02000);
					tbl[0] = 0x27 | (1 << 15) | (0x25 << 16);	// type | command=true | command register
					tbl[1] = 0 | (1 << 30);						// LBA 23:0 | device register
					tbl[2] = 0;									// LBA 47:24 | feature register
					tbl[3] = 1;//0x2000;									// count 15:0 | ICC | control register
					tbl[4] = 0;									// reserved

					tbl[32] = 0x4b00000;			// data base address
					tbl[33] = 0;					// data base address upper 32 bits
					tbl[34] = 0;					// reserved
					tbl[35] = 511;// | (1 << 31);//0x3fffff;//	// data byte count + interrupt on completion (bit 31)

					// set the command issue bit for slot 0 (bit 0)
					__asm__ __volatile__("mfence":::"memory");
					abar[reg_base_num + 14] = 1;
					__asm__ __volatile__("mfence":::"memory");

					// spin until data is received
					while(1)
					{
						__asm__ __volatile__("mfence":::"memory");
						uint32_t in = abar[reg_base_num + 14];
						__asm__ __volatile__("mfence":::"memory");

						if((in & 1) == 0)
						{
							break;
						}
					}

					/*
					// spin on PxIS.DPS
					while(1)
					{
						__asm__ __volatile__("mfence":::"memory");
						uint32_t in = abar[reg_base_num + 4];
						__asm__ __volatile__("mfence":::"memory");

						if((in & (1 << 5)) == 1)
						{
							break;
						}
					}*/

					__asm__ __volatile__("mfence":::"memory");
					uint32_t in = abar[reg_base_num + 4];
					__asm__ __volatile__("mfence":::"memory");

					if(in & (1 << 30))	// check for task file error
					{
						printf("\nTASK FILE ERROR");
						__KLIB__WaitThisThread();
					}



					// stop command running and received FIS
					__asm__ __volatile__("mfence":::"memory");
					PxCMD = abar[reg_base_num + 6];
					__asm__ __volatile__("mfence":::"memory");
					abar[reg_base_num + 6] = PxCMD & 0xffffffee;
					__asm__ __volatile__("mfence":::"memory");

					// wait until command running and received FIS running are zero
					__asm__ __volatile__("mfence":::"memory");
					PxCMD = abar[reg_base_num + 6];
					__asm__ __volatile__("mfence":::"memory");
					while(PxCMD & 0xc000)
					{
						__asm__ __volatile__("mfence":::"memory");
						uint32_t PxCMD = abar[reg_base_num + 6];
						__asm__ __volatile__("mfence":::"memory");
					}

					// check bootsig (400 bytes into MBR)
					const char *sig = "RAILGUN OS VERSION 0.4a  MBR BOOTSIG CXG98RKW";
					char *lsig = (char*)(KOFFSET + 0x4b00000 + 400);

					int k;
					for(k = 0; k < 46; k++)
					{
						if(sig[k] != lsig[k])
						{
							break;
						}
					}

					if(k == 46)
					{
						// found the MBR of the boot disk
						//printf("\nSignature found!!");
						boot_drive = j;
						boot_device = device_handles[i];
						boot_key = device_keys[i];
					}
					else
					{
						//printf("\nSignature not found");
					}


/*
					uint64_t *mbr = (uint64_t*)(KOFFSET + 0x4b00000 + 0x1be);
					for(k = 0; k < 4; k++)
					{
						uint8_t boot = mbr[0 + (k*2)] & 0xff;
						uint32_t lba_start = mbr[1 + (k*2)] & 0xffffffff;
						uint32_t sector_count = mbr[1 + (k*2)] >> 32;
						printf("\nMBR entry %u boot ID: 0x%x LBA start: %u total sectors: %u", k, boot, lba_start, sector_count);
					}*/


					// zero the read in data
					char *z = (char*)(KOFFSET + 0x4b00000);
					for(k = 0; k < 512; k++)
					{
						z[k] = 0;
					}
				}
			}
		}
	}

	// unreserve other devices
	for(i = 0; i < device_count; i++)
	{
		if(device_handles[i] != boot_device)
		{
			__KLIB__PCIEFreeDevice(device_handles[i], device_keys[i]);
		}
	}

	// check if boot device is present
	if(!boot_device)
	{
		return AHCI_INIT_FAIL_BOOT_NOT_PRESENT;
	}

	// get an ISR vector
	ISR_VECTOR isr_vectors[1];
	uint64_t isr_keys[1];
	error = __KLIB__ISRReserve(0x4, 1, isr_vectors, isr_keys);
	if(error != IRQ_SUCCESS)
	{
		return AHCI_INIT_FAIL_NO_ISR;
	}

	// register the ISR
	error = __KLIB__ISRRegister(isr_vectors[0], isr_keys[0], AHCI_ISR, IRQ_CPL_KERNEL, IRQ_GATE_INTERRUPT, IRQ_IST_3);
	if(error != IRQ_SUCCESS)
	{
		return AHCI_INIT_FAIL_NO_IRQ;
	}

	// save the ISR details
	g_vector = isr_vectors[0];
	g_isr_key = isr_keys[0];

	// now need to properly init the boot drive's controller and the drive itself
	// enable MSI in the PCIE config space
	// get base of capabilities list
	uint32_t cap_base = 0x34 / 4;
	__KLIB__PCIEConfigRead(boot_device, boot_key, cap_base, &cap_base);

	// capabilites search loop
	while(1)
	{
		// read in the first dword of the capabilities structure
		uint32_t buff;
		cap_base = cap_base / 4;
		__KLIB__PCIEConfigRead(boot_device, boot_key, cap_base, &buff);

		// get capability ID and ptr to next capability
		uint32_t cap_id = buff & 0xff;
		uint32_t next_cap = (buff >> 8) & 0xff;

		// check if MSI structure
		if(cap_id == 0x05)
		{
			// is MSI structure, configure it
			// read in the MSI control value
			uint32_t MSI_control;
			__KLIB__PCIEConfigRead(boot_device, boot_key, cap_base, &MSI_control);

			// get MSI address and message
			uint32_t MSI_address, MSI_message;
			__KLIB__GetMSIDestination(&MSI_address);
			MSI_message = isr_vectors[0] & 0xff;

			// cap_base points to the first DWORD of the MSI structure
			// write message address and message
			if(MSI_control & (1 << 23))
			{
				// is 64 bit capable
				// write low and high address
				__KLIB__PCIEConfigWrite(boot_device, boot_key, cap_base + 1, MSI_address);
				__KLIB__PCIEConfigWrite(boot_device, boot_key, cap_base + 2, 0);

				// write data
				__KLIB__PCIEConfigWrite(boot_device, boot_key, cap_base + 3, MSI_message);
			}
			else
			{
				// is only 32 bit capable
				// just write low
				__KLIB__PCIEConfigWrite(boot_device, boot_key, cap_base + 1, MSI_address);

				// write data
				__KLIB__PCIEConfigWrite(boot_device, boot_key, cap_base + 2, MSI_message);
			}

			// write control to enable MSI
			MSI_control = MSI_control & 0x008effff;	// clear MME count
			MSI_control = MSI_control | 0x00010000;	// enable MSI
			__KLIB__PCIEConfigWrite(boot_device, boot_key, cap_base, MSI_control);

			/*
			uint32_t tst;
			__KLIB__PCIEConfigRead(boot_device, boot_key, cap_base, &tst);
			printf("\n0x%a", tst);
			__KLIB__PCIEConfigRead(boot_device, boot_key, cap_base + 1, &tst);
			printf("\n0x%a", tst);
			__KLIB__PCIEConfigRead(boot_device, boot_key, cap_base + 2, &tst);
			printf("\n0x%a", tst);
			__KLIB__PCIEConfigRead(boot_device, boot_key, cap_base + 3, &tst);
			printf("\n0x%a", tst);
			*/

			break;
		}

		// not the MSI cap structure, get ptr to next one
		cap_base = next_cap;
		if(cap_base == 0)
		{
			break;
		}
	}

	// get base address of controller
	uint32_t base_address;
	__KLIB__PCIEConfigRead(boot_device, boot_key, 0x24/4, &base_address);
	volatile uint32_t *abar = (uint32_t*)(uint64_t)base_address;
	boot_abar = abar;

	// enable interrupts in the HBA
	__asm__ __volatile__("mfence":::"memory");
	uint32_t GHC = abar[1];
	__asm__ __volatile__("mfence":::"memory");
	GHC = GHC | (1 << 31) | (1 << 1);		// AHCI mode enable + interrupts enable
	abar[1] = GHC;
	__asm__ __volatile__("mfence":::"memory");

	// clear IS register by writing to all bits (bits are defined as RWC)
	__asm__ __volatile__("mfence":::"memory");
	abar[2] = 0xffffffff;
	__asm__ __volatile__("mfence":::"memory");

	// get base of boot port's registers
	uint32_t reg_base_num = (0x100 + (boot_drive * 0x80)) / 4;

	// enable the interrupts in PxIE (5)
	__asm__ __volatile__("mfence":::"memory");
	abar[reg_base_num + 5] = (1 << 30) | (1 << 24) | (1 << 5); // TFES + OFS + DPS
	__asm__ __volatile__("mfence":::"memory");

	// set command list and received FIS base addresses
	__asm__ __volatile__("mfence":::"memory");
	abar[reg_base_num + 0] = 0x4a00000;
	abar[reg_base_num + 1] = 0;
	abar[reg_base_num + 2] = 0x4a01000;
	abar[reg_base_num + 3] = 0;
	__asm__ __volatile__("mfence":::"memory");

	// restart command processing and make sure device is active
	__asm__ __volatile__("mfence":::"memory");
	uint32_t PxCMD = abar[reg_base_num + 6];
	__asm__ __volatile__("mfence":::"memory");
	abar[reg_base_num + 6] = PxCMD | 0x11 | (1 << 28);
	__asm__ __volatile__("mfence":::"memory");

	// wait until command running and received FIS running are one
	__asm__ __volatile__("mfence":::"memory");
	PxCMD = abar[reg_base_num + 6];
	__asm__ __volatile__("mfence":::"memory");
	while((PxCMD & 0xc000) != 0xc000)
	{
		__asm__ __volatile__("mfence":::"memory");
		uint32_t PxCMD = abar[reg_base_num + 6];
		__asm__ __volatile__("mfence":::"memory");
	}




	// finished success
	return AHCI_INIT_SUCCESS;
}

/*
	// zero the AHCI data area
	uint64_t *data_area = (uint64_t*)(KOFFSET + 0x4a00000);
	uint32_t i;
	for(i = 0; i < 0x100000; i++)
	{
		data_area[i] = 0;
	}

	// first need to find all of the AHCI controllers on the PCI-E bus
	// get address of PCI-E MCFG table
	ACPI_MCFG *mcfg = ACPIGetMCFGBase();
	if(!mcfg)
	{
		// this will likely change soon - if no PCI-E available, will attempt to default to PCI
		KERNEL_ERROR("PCI-E enumeration", "AHCI_init", "ACPI MCFG table not found");
	}

	// get base address of system config table
	TBL_SYSCONFIG *config_base = __KLIB__GetSystemConfigBase();

	// get number of bus config spaces
	uint32_t config_spaces = mcfg->length - 44;
	config_spaces /= 16;

	// list containing address values for all usable AHCI controller PCIE config spaces
	uint64_t AHCI_spaces[32];
	uint32_t AHCI_count = 0;

	// for each MCFG address space
	for(i = 0; i < config_spaces; i++)
	{
		// get base address of space and number of buses within the space
		uint32_t *pcie_base = (uint32_t*)mcfg->entries[i].config_base_address;
		uint32_t bus_count = mcfg->entries[i].end_PCI_bus_number - mcfg->entries[i].start_PCI_bus_number + 1;

		// for each bus
		uint32_t b, d, f;
		for(b = 0; b < bus_count; b++)
		{
			// for each device
			for(d = 0; d < 32; d++)
			{
				// for each function
				for(f = 0; f < 8; f++)
				{
					// check the function exists (use vendor ID)
					__asm__ __volatile__("mfence":::"memory");
					uint32_t vendor_ID = pcie_base[0] & 0xffff;
					__asm__ __volatile__("mfence":::"memory");
					if(vendor_ID != 0xffff)
					{
						// now get device class code
						__asm__ __volatile__("mfence":::"memory");
						uint32_t class_code = pcie_base[2] >> 8;
						__asm__ __volatile__("mfence":::"memory");

						// check if for AHCI controller
						if(class_code == 0x010601)
						{
							// found an AHCI controller
							// now need to check it supports Message Signalled Interrupts
							// check PCI express capability for the device
							// get PCI capability pointer
							__asm__ __volatile__("mfence":::"memory");
							uint32_t cap_ptr = pcie_base[13] & 0xfc;
							__asm__ __volatile__("mfence":::"memory");

							// get cap IDs
							while(cap_ptr != 0x00)
							{
								// get the CAP ID and the pointer to the next cap (if it exists)
								__asm__ __volatile__("mfence":::"memory");
								uint32_t cap_ID = pcie_base[cap_ptr / 4] & 0xff;
								__asm__ __volatile__("mfence":::"memory");
								uint32_t next_cap = pcie_base[cap_ptr / 4] >> 8;
								__asm__ __volatile__("mfence":::"memory");
								cap_ptr = next_cap & 0xfc;

								// check the CAP ID for MSI capabilities
								// MSI capabilities presence indicates that the device supports MSI
								if(cap_ID == 0x05 && AHCI_count < 32)
								{
									// AHCI controller is valid
									AHCI_spaces[AHCI_count++] = (uint64_t)pcie_base;

									printf("\nfound controller on PCI bus %u, device %u, function %u", b, d, f);

									break;
								}
							}
						}
					}

					// finished, point to the next config space
					pcie_base += (0x1000 / 4);
				}
			}
		}
	}

	// check for AHCI devices found
	if(!AHCI_count)
	{
		KERNEL_ERROR("PCI-E enumeration", "AHCI_init", "No MSI capable AHCI devices found or no AHCI devices found");
	}

	// PCIE config area, base of controller and port number of the boot drive
	uint32_t *boot_pcie = NULL;
	uint32_t *boot_ahci = NULL;
	uint32_t boot_port = 0xffffffff;

	// now need to fully software initialise each valid AHCI controller
	for(i = 0; i < AHCI_count; i++)
	{
		// get pointer to PCI Express config area for this AHCI controller
		printf("\nAHCI device: %u", i);
		uint32_t *config = (uint32_t*)AHCI_spaces[i];

		// get pointer to start of controller space
		__asm__ __volatile__("mfence":::"memory");
		uint32_t *abar = (uint32_t*)(uint64_t)config[9];
		__asm__ __volatile__("mfence":::"memory");
		printf(" ABAR: 0x%a", abar);

		// enable the device in AHCI mode and disable interrupts
		__asm__ __volatile__("mfence":::"memory");
		uint32_t GHC = abar[1];
		__asm__ __volatile__("mfence":::"memory");
		GHC = GHC | (1 << 31);		// AHCI mode
		GHC = GHC & 0xfffffffd;		// global disable interrupts
		abar[1] = GHC;
		__asm__ __volatile__("mfence":::"memory");


		// get implemented ports & details for each port
		__asm__ __volatile__("mfence":::"memory");
		uint32_t ports = abar[3];
		__asm__ __volatile__("mfence":::"memory");
		printf("\nImplemented ports: 0x%x", ports);
		int j;
		for(j = 0; j < 32; j++)
		{
			// check if port is available
			if((ports >> j) & 1)
			{
				// port is available, get the physical connection state
				uint32_t reg_base_num = (0x100 + (j * 0x80)) / 4;
				__asm__ __volatile__("mfence":::"memory");
				uint32_t state = abar[reg_base_num + 10];
				__asm__ __volatile__("mfence":::"memory");
				uint32_t IPM, SPD, DET;
				IPM = state >> 8;
				SPD = (state >> 4) & 0xf;
				DET = state & 0xf;

				printf("\nPort: %u IPM: 0x%x SPD: 0x%x DET: 0x%x", j, IPM, SPD, DET);

				// check if port is detected and powered on
				if(DET == 0x3 && IPM == 0x1)
				{
					boot_pcie = config;
					boot_port = j;
					boot_ahci = abar;
					printf(" <- this one");
				}
			}
		}
	}

	// check for boot device found
	if(!boot_pcie)
	{
		KERNEL_ERROR("PCI-E enumeration", "AHCI_init", "Could not find boot device");
	}

	// read first sector from boot device
	printf("\nnow attempting to read from device at 0x%a port %u", boot_ahci, boot_port);
	volatile uint32_t *abar = boot_ahci;
	uint32_t port_base = (0x100 + (0x80 * boot_port)) / 4;

	// stop command running and received FIS
	__asm__ __volatile__("mfence":::"memory");
	uint32_t PxCMD = abar[port_base + 6];
	__asm__ __volatile__("mfence":::"memory");
	abar[port_base + 6] = PxCMD & 0xffffffee;
	__asm__ __volatile__("mfence":::"memory");

	// wait until command running and received FIS running are zero
	__asm__ __volatile__("mfence":::"memory");
	PxCMD = abar[port_base + 6];
	__asm__ __volatile__("mfence":::"memory");
	while(PxCMD & 0xc000)
	{
		__asm__ __volatile__("mfence":::"memory");
		uint32_t PxCMD = abar[port_base + 6];
		__asm__ __volatile__("mfence":::"memory");
	}

	// command processing has stopped
	// set command list and received FIS base addresses
	__asm__ __volatile__("mfence":::"memory");
	abar[port_base + 0] = 0x4a00000;
	abar[port_base + 1] = 0;
	abar[port_base + 2] = 0x4a01000;
	abar[port_base + 3] = 0;
	__asm__ __volatile__("mfence":::"memory");

	// restart command processing and make sure device is active
	__asm__ __volatile__("mfence":::"memory");
	abar[port_base + 6] = PxCMD | 0x11 | (1 << 28);
	__asm__ __volatile__("mfence":::"memory");

	// wait until command running and received FIS running are one
	__asm__ __volatile__("mfence":::"memory");
	PxCMD = abar[port_base + 6];
	__asm__ __volatile__("mfence":::"memory");
	while((PxCMD & 0xc000) != 0xc000)
	{
		__asm__ __volatile__("mfence":::"memory");
		uint32_t PxCMD = abar[port_base + 6];
		__asm__ __volatile__("mfence":::"memory");
	}

	// construct command table and command header 0
	uint32_t *cmd = (uint32_t*)(KOFFSET + 0x4a00000);
	cmd[0] = (1 << 16) | (1 << 10) | 5;
	cmd[1] = 0;
	cmd[2] = 0x4a02000;
	cmd[3] = 0;
	cmd[4] = 0;
	cmd[5] = 0;
	cmd[6] = 0;
	cmd[7] = 0;

	uint32_t *tbl = (uint32_t*)(KOFFSET + 0x4a02000);
	tbl[0] = 0x27 | (1 << 15) | (0x25 << 16);
	tbl[1] = 0 | (1 << 30);
	tbl[2] = 0;
	tbl[3] = 8;
	tbl[4] = 0;

	tbl[32] = 0x4b00000;
	tbl[33] = 0;
	tbl[34] = 0;
	tbl[35] = 0xfffff;


	// set the command issue bit for slot 0 (bit 0)
	__asm__ __volatile__("mfence":::"memory");
	abar[port_base + 14] = 1;
	__asm__ __volatile__("mfence":::"memory");

	__KLIB__SleepThisThread(5000000);


	printf("\ndone");

	uint16_t *bootsig = (uint16_t*)(KOFFSET + 0x4b00000 + 510);
	printf("\nbootsig: 0x%x", *bootsig);

	// print MBR entries (cos I can, that's why)
	uint64_t *mbr = (uint64_t*)(KOFFSET + 0x4b00000 + 0x1be);
	for(i = 0; i < 4; i++)
	{
		uint8_t boot = mbr[0 + (i*2)] & 0xff;
		uint32_t lba_start = mbr[1 + (i*2)] & 0xffffffff;
		uint32_t sector_count = mbr[1 + (i*2)] >> 32;

		printf("\nMBR entry %u boot ID: 0x%x LBA start: %u total sectors: %u", i, boot, lba_start, sector_count);
	}

*/
